<?php

namespace Xin\Coze;

use Xin\Capsule\Service;
use Xin\Coze\Providers\AudioProvider;
use Xin\Coze\Providers\BotProvider;
use Xin\Coze\Providers\ChatProvider;
use Xin\Coze\Providers\ConversationProvider;
use Xin\Coze\Providers\DatasetsProvider;
use Xin\Coze\Providers\FilesProvider;
use Xin\Coze\Providers\WorkflowProvider;
use Xin\Coze\Providers\WorkspacesProvider;
use Xin\HttpClient\HasHttpClient;
use Xin\HttpClient\Response;
use Xin\Support\Arr;

/**
 * CozeAPI Service.
 * @method WorkspacesProvider workspaces()
 * @method BotProvider bot()
 * @method BotProvider datasets()
 * @method ConversationProvider conversation()
 * @method ChatProvider chat()
 * @method WorkflowProvider workflow()
 * @method AudioProvider audio()
 * @method FilesProvider files()
 */
class CozeService extends Service
{
	use HasHttpClient;

	/**
	 * @var AccessToken
	 */
	protected $accessToken;

	/**
	 * @var array
	 */
	protected $providers = [];

	/**
	 * @var array
	 */
	protected $services = [];

	/**
	 * CozeService constructor.
	 * @param array $config
	 */
	public function __construct(array $config)
	{
		parent::__construct($config);

		$this->provider('workspaces', WorkspacesProvider::class);
		$this->provider('bot', BotProvider::class);
		$this->provider('chat', ChatProvider::class);
		$this->provider('conversation', ConversationProvider::class);
		$this->provider('datasets', DatasetsProvider::class);
		$this->provider('workflow', WorkflowProvider::class);
		$this->provider('audio', AudioProvider::class);
	}

	/**
	 * 获取 AccessToken
	 * @return AccessToken
	 */
	public function accessToken()
	{
		if (!$this->accessToken) {
			$this->accessToken = new AccessToken(
				$this->config['app_id'],
				$this->config['public_key_id'],
				$this->config['private_key'],
				$this->config['expired_time'] ?? null,
			);
		}

		return $this->accessToken;
	}

	/**
	 * @inerhitDoc
	 */
	public function httpClientOptions()
	{
		return array_replace_recursive([
			'base_uri' => 'https://api.coze.cn/',
			'timeout' => 15,
			// 'headers' => [
			// 	'Accept' => 'application/json',
			// 	'Content-Type' => 'application/json',
			// ],
		], $this->getConfig('http', []));
	}

	/**
	 * @inerhitDoc
	 */
	protected function onHttpClientRequest(array $options)
	{
		Arr::set($options, 'headers.Authorization', 'Bearer ' . $this->accessToken()->token()['access_token']);

		return $options;
	}

	/**
	 * @inerhitDoc
	 */
	protected function onHttpClientResponse(Response $response)
	{
		$response->throwUnlessHttpError();

		// 响应流
		if ($response->requestOptions()->get('stream', false) || $response->isSSE()) {
			return $response->steamIterator();
		}

		// 判断是否是音频或视频
		if ($response->isContentType('audio/') || $response->isContentType('video/')) {
			return $response;
		}

		// 判断业务状态码
		if ($response->data('code') !== 0) {
			throw new \LogicException($response->data('msg'), $response->data('code'));
		}

		return $response->data('data');
	}

	/**
	 * Http Get
	 * @param string $uri
	 * @param array $query
	 * @param array $options
	 * @return array|null
	 */
	public function httpGet(string $uri, array $query = [], array $options = [])
	{
		return $this->client()->get($uri, $query, $options);
	}

	/**
	 * Http Post
	 * @param string $uri
	 * @param array $data
	 * @param array $options
	 * @return mixed|array|null
	 */
	public function httpPost(string $uri, array $data = [], array $options = [])
	{
		$data = $data ?: new \stdClass();
		return $this->client()->post($uri, $data, $options);
	}

	/**
	 * Http Put
	 * @param string $uri
	 * @param array $data
	 * @param array $options
	 * @return array|null
	 */
	public function httpPut(string $uri, array $data = [], array $options = [])
	{
		$data = $data ?: new \stdClass();
		return $this->client()->put($uri, $data, $options);
	}

	/**
	 * Http Delete
	 * @param string $uri
	 * @param array $data
	 * @param array $options
	 * @return array|null
	 */
	public function httpDelete(string $uri, array $data = [], array $options = [])
	{
		$data = $data ?: new \stdClass();
		return $this->client()->delete($uri, $data, $options);
	}

	/**
	 * 获取服务
	 * @param string $name
	 * @return mixed
	 */
	public function service(string $name)
	{
		if (!isset($this->services[$name])) {
			$this->services[$name] = $this->makeService($name);
		}

		return $this->services[$name];
	}

	/**
	 * 创建服务
	 * @return mixed
	 * @throws \LogicException
	 */
	protected function makeService($name)
	{
		if (!isset($this->providers[$name])) {
			throw new \LogicException('Service not found');
		}

		$providerResolver = $this->providers[$name];
		if (is_callable($providerResolver)) {
			$service = call_user_func_array($providerResolver, [$this, $this->config]);
		} elseif (class_exists($providerResolver)) {
			if (method_exists($providerResolver, '__make')) {
				$service = call_user_func_array([$providerResolver, '__make'], [$this, $this->config]);
			} else {
				$service = new $providerResolver($this);
			}
		} else {
			throw new \LogicException('Service provider must be a class or callable.');
		}

		return $service;
	}

	/**
	 * @param string $name
	 * @param callable|class-string $makeResolver
	 * @return void
	 */
	public function provider(string $name, $makeResolver)
	{
		$this->providers[$name] = $makeResolver;
	}

	/**
	 * @param string $method
	 * @param array $parameters
	 * @return mixed
	 */
	public function __call(string $method, array $parameters)
	{
		return $this->service($method);
	}


	/**
	 * 设置缓存解析器
	 * @param callable $cacheResolver
	 * @return void
	 */
	public static function setCacheResolver(callable $cacheResolver)
	{
		AccessToken::setCacheResolver($cacheResolver);
	}
}
